package pers.chao.tool.infrastructure;

import sun.reflect.ConstructorAccessor;
import sun.reflect.FieldAccessor;
import sun.reflect.ReflectionFactory;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * Description:
 *
 * @author W.Chao
 * @date 2020/11/8 11:09
 **/
public final class DynamicEnumUtil {
	private DynamicEnumUtil() {
	}

	private static ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();

	private static void setFailsafeFieldValue(Field field, Object target, Object value) throws NoSuchFieldException,
			IllegalAccessException {

		// 设置字段可访问
		field.setAccessible(true);

		// 将字段实例对象中的修改符设置为非final的，
		// 从而可以利用反射修改静态的final对象
		Field modifiersField = Field.class.getDeclaredField("modifiers");
		modifiersField.setAccessible(true);
		int modifiers = modifiersField.getInt(field);

		// 将modifiers的final位反转，方便后面修改
		modifiers &= ~Modifier.FINAL;
		modifiersField.setInt(field, modifiers);

		// 利用反射工厂得到新的字段访问器，将新的字段值设置到实例中
		FieldAccessor fa = reflectionFactory.newFieldAccessor(field, false);
		fa.set(target, value);
	}

	private static void blankField(Class<?> enumClass, String fieldName) throws NoSuchFieldException,
			IllegalAccessException {
		for (Field field : Class.class.getDeclaredFields()) {
			if (field.getName().contains(fieldName)) {
				AccessibleObject.setAccessible(new Field[]{field}, true);
				setFailsafeFieldValue(field, enumClass, null);
				break;
			}
		}
	}

	private static void cleanEnumCache(Class<?> enumClass) throws NoSuchFieldException, IllegalAccessException {
		blankField(enumClass, "enumConstantDirectory"); // Sun (Oracle?!?) JDK 1.5/6
		blankField(enumClass, "enumConstants"); // IBM JDK
	}

	// 反射工厂创建构造器
	private static ConstructorAccessor getConstructorAccessor(Class<?> enumClass, Class<?>[] additionalParameterTypes)
			throws NoSuchMethodException {
		Class<?>[] parameterTypes = new Class[additionalParameterTypes.length + 2];
		parameterTypes[0] = String.class;
		parameterTypes[1] = int.class;
		System.arraycopy(additionalParameterTypes, 0, parameterTypes, 2, additionalParameterTypes.length);
		return reflectionFactory.newConstructorAccessor(enumClass.getDeclaredConstructor(parameterTypes));
	}

	private static Object makeEnum(Class<?> enumClass, String value, int ordinal, Class<?>[] additionalTypes,
								   Object[] additionalValues) throws Exception {
		Object[] parms = new Object[additionalValues.length + 2];
		parms[0] = value; // 枚举值的名字
		parms[1] = ordinal; // 枚举值的顺序
		System.arraycopy(additionalValues, 0, parms, 2, additionalValues.length);
		// 反射构造器创建新的枚举值
		return enumClass.cast(getConstructorAccessor(enumClass, additionalTypes).newInstance(parms));
	}

	/**
	 * 动态添加枚举值
	 *
	 * @param enumType         枚举类
	 * @param enumName         枚举值的名字
	 * @param additionalTypes  枚举值包含的变量的类型数组
	 * @param additionalValues 枚举值包含的变量值数组
	 * @param <T>
	 */
	@SuppressWarnings("unchecked")
	public static <T extends Enum<?>> void addEnum(Class<T> enumType, String enumName, Class<?>[] additionalTypes, Object[] additionalValues) {

		// 检查是否是枚举类型
		if (!Enum.class.isAssignableFrom(enumType)) {
			throw new RuntimeException("class " + enumType + " is not an instance of Enum");
		}

		// Lookup "$VALUES" holder in enum class and get previous enum instances
		// 获取该枚举的所有已存在枚举值并设置可访问
		Field valuesField = null;
		Field[] fields = enumType.getDeclaredFields();
		for (Field field : fields) {
			if (field.getName().contains("$VALUES")) {
				valuesField = field;
				break;
			}
		}
		AccessibleObject.setAccessible(new Field[]{valuesField}, true);

		try {

			// 复制已有的枚举值
			T[] previousValues = (T[]) valuesField.get(enumType);
			List<T> values = new ArrayList<T>(Arrays.asList(previousValues));

			// 构建新的枚举值
			T newValue = (T) makeEnum(enumType, enumName, values.size(), additionalTypes, additionalValues);

			// 添加到暂存数组中
			values.add(newValue);

			// 将暂存的数组值(包括已有的和动态添加的枚举)设置到枚举对象中
			setFailsafeFieldValue(valuesField, null, values.toArray((T[]) Array.newInstance(enumType, 0)));

			// Clean enum cache(清除枚举类缓存)
			cleanEnumCache(enumType);

		} catch (Exception e) {
			throw new RuntimeException(e.getMessage(), e);
		}
	}
}
